/*
 * Copyright (c) 2021, NVIDIA CORPORATION & AFFILIATES. All rights reserved.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 * http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

#ifndef VIRTNET_H
#define VIRTNET_H

#include <stdio.h>
#include <stdlib.h>
#include <stdint.h>
#include <string.h>
#include <stdbool.h>
#include <argp.h>
#include <time.h>
#include <errno.h>
#include <malloc.h>
#include <assert.h>
#include <unistd.h>
#include <pthread.h>
#include <semaphore.h>

#include <net/if.h>
#include <net/ethernet.h>
#include <linux/virtio_pci.h>
#include <linux/virtio_net.h>
#include <infiniband/verbs.h>
#include <infiniband/mlx5dv.h>

#include <snap.h>
#include <snap_dma.h>
#include <snap_virtio_net.h>
#include <snap_virtio_net_ctrl.h>
#include <snap_virtio_common_ctrl.h>

#include "mlx5_ifc.h"
#include "virtnet_sf.h"
#include "virtnet_vq.h"
#include "virtnet_devx.h"
#include "virtnet_util.h"
#include "virtnet_recovery.h"

#include "mlxdevm.h"

#ifdef __cplusplus
extern "C" {
#endif

#define VIRTIO_PCI_VENDORID			0x1AF4
#define VIRTIO_PCI_SUBSYSTEM_ID			0x01
#define VIRTIO_PCI_SUBSYSTEM_VENDOR_ID		VIRTIO_PCI_VENDORID
#define VIRTIO_PCI_MODERN_DEVICEID_NET		0x1041
#define VIRTIO_PCI_LEGACY_DEVICEID_NET		0x1000
#define VIRTIO_PCI_REV				0x0
#define VIRTIO_PCI_CLASS_NETWORK_ETHERNET	0x020000

#define VIRTIO_NET_F_CTRL_GUEST_OFFLOADS	2
#define VIRTIO_F_RING_PACKED			34
#define VIRTIO_F_ORDER_PLATFORM			36
#define VIRTIO_F_SR_IOV				37
#define VIRTIO_NET_F_STANDBY			62
#define VIRTIO_NET_F_SPEED_DUPLEX		63

#ifndef VIRTIO_F_IOMMU_PLATFORM
#define VIRTIO_F_IOMMU_PLATFORM			33
#endif

#define MAX_FEATURES ((1ULL << VIRTIO_F_SR_IOV)		| \
		      (1ULL << VIRTIO_F_IOMMU_PLATFORM) | \
		      (1ULL << VIRTIO_F_VERSION_1))	| \
		      (1ULL << VIRTIO_NET_F_MTU)	| \
		      (1ULL << VIRTIO_NET_F_MAC)	| \
		      (1ULL << VIRTIO_NET_F_STATUS)

struct virtnet_context;
extern const char *ib_dev_names[];

enum virtnet_ib_ports {
	IB_DEV_P0		= 0,
	IB_DEV_P1		= 1,
	IB_DEV_LAG		= 2,
	IB_NUM_PORTS		= 3,

	/* Only one emulation manager device per host */
	IB_DEV_EMU_MANAGER	= IB_DEV_P0,
	IB_DEV_EMU_MANAGER_LAG	= IB_DEV_LAG,
};

/**
 * virtnet initialization attributes
 *
 * @ib_dev_name: IB device name used for static configured devices
 * @is_lag: Whether or not LAG is configured
 */
struct virtnet_init_attr {
	char ib_dev_name[IBV_SYSFS_NAME_MAX];
	bool			is_lag;
	uint64_t		pf_mac;
};

/**
 * virtnet IB port context
 *
 * @dev: IB device for this port
 * @ib_ctx: IB device context for monitoring interrupt
 * @pci_addr_str: PCI address string for this IB dev
 * @pci_function: PCI function number for this IB dev
 * @devm: mlxdevm connection for SF management
 * @virtnet_ctx: virtnet context for update devices
 * @epoll_data: epoll data for interrupt handler
 */
struct virtnet_port_ctx {
	struct ibv_device *dev;
	struct ibv_context *ib_ctx;
	char pci_addr_str[PCI_ADDR_STR_LEN + 1];
	uint8_t pci_function;
	uint16_t mtu;

	struct mlxdevm * devm;
	struct virtnet_context *virtnet_ctx;
	struct virtnet_epoll_data *epoll_data;
	uint32_t max_sf_num;
};

/**
 * Callback function to handle device
 *
 * This callback is called when there are pre-configured devices (hotplug and
 * static PF/its VFs) detected. It gives user the chance to handle the device.
 * E.g, calling @virtnet_device_open
 *
 * To get the viretnet_device pointer, call @virtnet_device_get with pci
 * bdf information.
 *
 * @dev:	Pointer to device
 * @return:	0 on success, error otherwise
 */
typedef int (*virtnet_device_handler_cb_t)(struct virtnet_device *dev);

/**
 * virtnet context for init mlx virtnet controller
 *
 * @emu_ib_dev:	  IB device which acts as emulation manager
 * @ib_dev:	  IB device that SFs will be created on, for static PF only
 * @sctx:	  Snap context for emulation manager
 * @dev_list:	  List of physical devices
 * @port_ctx:	  Context for IB port
 * @handler:	  Callback function to handle pre-configured device
 * @max_features: Max feature bits supported by emulation manager
 * @wq_tid:	  Pthread tid for device work queue
 * @wq_sem:	  Semaphore used to sync between event handler and workqueue
 */
struct virtnet_context {
	struct ibv_device		*emu_ib_dev;
	struct ibv_device		*ib_dev;
	struct snap_context		*sctx;
	struct virtnet_device		*dev_list;
	struct virtnet_port_ctx		*port_ctx[IB_NUM_PORTS];
	virtnet_device_handler_cb_t	handler;
	uint64_t			max_features;
	pthread_t			wq_tid;
	sem_t				wq_sem;

	struct udev			*udev;
	struct udev_monitor		*monitor;
};

/**
 * virtnet device creation attributes
 *
 * @ib_dev:	IB device which SFs will be created on top of
 * @features:	supported feature bit map for this device
 *		see VIRTIO_F_X/VIRTIO_NET_F_X
 * @mac:	mac address of this device
 * @mtu:	mtu of this device
 * @num_queues: number of virtqueues for this device
 * @queue_size: queue size of each virtqueue
 */
struct virtnet_device_attr {
	struct ibv_device	*ib_dev;
	uint64_t		features;
	struct ether_addr	mac;
	uint16_t		mtu;
	uint16_t		num_queues;
	uint16_t		queue_size;
};

/**
 * virtnet device state/status flags
 *
 * @VIRTNET_DEV_PF: Device is a physical function
 * @VIRTNET_DEV_VF: Device is a virtual function
 * @VIRTNET_DEV_PRE_CONFIG: Device is a pre configured function via firmware
 * @VIRTNET_DEV_STARTED: Device has started
 * @VIRTNET_DEV_OPEN: Device is opened
 */
enum virtnet_dev_flag {
	VIRTNET_DEV_PF		= 1U << 0,
	VIRTNET_DEV_VF		= 1U << 1,
	VIRTNET_DEV_PRE_CONFIG	= 1U << 2,
	VIRTNET_DEV_STARTED	= 1U << 3,
	VIRTNET_DEV_OPENED	= 1U << 4,
};

/**
 * Optional callback function to handle control commands
 *
 * @dev:		 Pointer to device
 * @virtio_net_ctrl_hdr: Control virtqueue data structures
 * @virtnet_iovec:	 Points to an array of iovec structures, it doesn't have
 *			 the ack included. Ack is handled by mlx controller.
 * @iov_cnt:		 Count of iovec
 * @return:		 0 on success, error otherwise
 *
 * User can register the callback by passing the function pointer to cmds_cb
 * from @virtnet_device when @virtnet_device_open
 *
 * The callback is called from within virtnet_vq_ctrl_cmds_parse() when new
 * cmds are received from the emulated device.
 *
 * Libvirtnet handles cmds below, the callback(if registered) will
 * handle all the other cmds.
 *
 *    VIRTIO_NET_CTRL_RX
 *    VIRTIO_NET_CTRL_MAC
 *    VIRTIO_NET_CTRL_VLAN
 *    VIRTIO_NET_CTRL_ANNOUNCE
 *    VIRTIO_NET_CTRL_MQ
 */
typedef int (*virtnet_crtl_cmds_cb_t)(struct virtnet_device *dev,
				      struct virtio_net_ctrl_hdr ctrl,
				      struct virtnet_iovec *iov,
				      uint32_t iov_cnt);

/**
 * virtnet rx mode fileds
 */
union virtnet_rx_mode {
	uint32_t val;
	struct {
		unsigned int promisc:1;
		unsigned int allmulti:1;
		unsigned int alluni:1;
		unsigned int nomulti:1;
		unsigned int nouni:1;
		unsigned int nobcast:1;
	};
};

/**
 * virtnet device creation attributes
 *
 * @ctx:  virtnet context for this device. Created by @virtnet_context_init
 * @port_ctx: IB port context which the SF is created on top of
 * @flag: device state/status flag, @virtnet_dev_flag
 * @pci_bdb: pci bdf for this device
 * @cmds_cb: control commands callback. Should be registered via
 *           virtnet_register_ctrl_cmd_cb after device creation
 * @vf_dev:  virtnet device list for VFs, valid for PF devices
 * @num_of_vfs: number of VFs enabled for this PF(if valid)
 */
struct virtnet_device {
	struct virtnet_context			*ctx;
	struct virtnet_port_ctx			*port_ctx;
	uint32_t				flag;
	uint16_t				pci_bdf;
	uint16_t				id;
	struct virtnet_epoll_data		epoll;
	pthread_mutex_t				lock;
	bool					recover;

	struct virtnet_device			*vf_dev;
	uint16_t				num_of_vfs;

	virtnet_crtl_cmds_cb_t			cmds_cb;

	struct snap_pci				*snap_pci;
	struct snap_device			*snap_dev;
	struct snap_virtio_net_ctrl		*snap_ctrl;
	struct snap_virtio_net_device_attr	*registers;
	struct snap_virtio_net_queue_attr	*reg_vqs;

	struct virtnet_sf_context		sf_verbs;
	struct virtnet_ctrl_qp			ctrl;

	struct virtnet_vq			*eth_vqs;
	uint16_t				num_eth_queues;
	struct virtnet_vq			ctrl_vq;
	uint8_t					vq_pair_n;

	struct virtnet_mac_table_entry		mac;
	struct virtnet_mac_table		mac_table;
	struct virtnet_vlan_table		vlan_table;

	union virtnet_rx_mode			rx_mode;
	struct virtnet_queue_period		period;
};

/**
 * Status bits for guest to report progress, and synchronize features
 */
enum virtnet_device_status {
	STATUS_RESET			= 0,
	STATUS_ACKNOWLEDGE		= 1U << 0,
	STATUS_DRIVER			= 1U << 1,
	STATUS_DRIVER_OK		= 1U << 2,
	STATUS_FEATURES_OK		= 1U << 3,
	STATUS_DEVICE_NEEDS_RESET	= 1U << 6,
	STATUS_FAILED			= 1U << 7,
};

/**
 * virtnet device modifiable fields bit mask
 */
enum virtnet_device_modify_fields_mask {
	VIRTNET_MODIFY_STATE	= 1UL << 0,
	VIRTNET_MODIFY_LINK	= 1UL << 1,
	VIRTNET_MODIFY_MAC	= 1UL << 2,
	VIRTNET_MODIFY_FEATURES = 1UL << 3,
	VIRTNET_MODIFY_MTU	= 1UL << 4,
};

/**
 * virtnet device modifiable fields
 *
 * @state:	device state, @virtnet_device_status
 * @link_up:	link state of device, true-up, false-down
 * @mac:	mac address of this device
 * @features:	device features, see VIRTIO_F_X/VIRTIO_NET_F_X
 * @mtu:	device mtu
 */
struct virtnet_device_modify_fields {
	uint16_t		state;
	bool			link_up;
	struct ether_addr	mac;
	uint64_t		features;
	uint16_t		mtu;
};

/**
 * dirty memory map mode
 *
 * @DIRTY_BIT:  each bit represents one dirty page
 * @DIRTY_BYTE: each byte represents one dirty page
 */
enum dirty_bmap_mode {
	DIRTY_BIT,
	DIRTY_BYTE,
};

/**
 * dirty memory bmap memory location
 *
 * @DIRTY_BMAP_REMOTE: bmap memory is allocated on remote side
 * @DIRTY_BMAP_LOCAL:  Bmap memory is allocated on local side
 */
enum dirty_bmap_access_flag {
	DIRTY_BMAP_REMOTE,
	DIRTY_BMAP_LOCAL,
};

/**
 * dirty memory bmap memory location
 *
 * @flag: indicates where the memory(@dirty_bmap_addr) is allocated
 * @mode: indicates dirty page is tracked by each bit or byte
 * @dirty_bmap_addr:  dirty bmap memory address
 * @dirty_bmap_size:  dirty bmap memory address
 * @guest_page_size:  page size the dirty bmap is tracking
 */
struct virtnet_migration_log {
	enum dirty_bmap_access_flag	flag;
	enum dirty_bmap_mode		mode;

	uint64_t dirty_bmap_addr;
	uint32_t dirty_bmap_size;
	uint32_t guest_page_size;
};

/******************************************************/
/****************** device operation ******************/
/******************************************************/

/**
 * Init virtnet context and do necessary preparation for controller.
 * This should be the first step to bringup the controller.
 *
 * @param attr
 *	virtnet initialization attributes
 * @param handler
 *	callback function to handle device
 * @return
 * 	valid virtnet_context pointer on success, NULL on error
 */
struct virtnet_context *virtnet_init(struct virtnet_init_attr *attr,
				     virtnet_device_handler_cb_t handler);
/**
 * Destroy virtnet context
 *
 * @param virtnet_context
 *	virtnet context for this emulation manager
 */
void virtnet_destroy(struct virtnet_context *ctx);

/**
 * Open hotplug device
 *
 * @param virtnet_context
 *	virtnet context for this emulation manager
 * @param attr
 *	pointer to device attributes, caller should specify each attribute.
 * @return
 * 	pointer to device if created successfully. NULL if failed.
 */
struct virtnet_device *
virtnet_device_hotplug(struct virtnet_context *ctx,
		       struct virtnet_device_attr *attr);

/**
 * Destroy hotplug device
 *
 * @param dev
 *	pointer to device
 */
void virtnet_device_unplug(struct virtnet_device *dev);

/**
 * Open device(hotplug or static, PF or VF)
 *
 * @param dev
 *	pointer to device
 * @return
 * 	0 on success, error otherwise
 */
int virtnet_device_open(struct virtnet_device *dev);

/**
 * Close device(hotplug or static)
 *
 * @param dev
 *	pointer to device
 */
void virtnet_device_close(struct virtnet_device *dev);

/**
 * Modify device attributes
 *
 * @param dev
 *	pointer to device
 * @param val
 *	pointer to modified field(s)
 * @param mask
 *	bit mask to indicates which field(s) to modify
 * @return
 * 	0 on success, error otherwise
 */
int virtnet_device_modify(struct virtnet_device *dev,
			  struct virtnet_device_modify_fields *val,
			  uint64_t mask);

/**
 * Get virtnet device by looking up pci bdf
 *
 * @param pci_bdf
 *	pci bdf value raw data
 * @return
 *	valid pointer to device if found. NULL if failed.
 */
struct virtnet_device *virtnet_device_get(uint16_t pci_bdf);

#ifdef __cplusplus
}
#endif

#endif
